function [dat,sr] = synth_nmat(nmat, varargin)
% function [dat,sr] = synth_nmat(nmat, varargin)
%
% Uses timidity to synthesize a note matrix and return the audio.
%
% Input:
%   nmat     [mat]  - Note matrix to render
%  OPTIONAL (passed in name-value pairs):
%   synth    [str]  - Software synthesizer to use.  ['timidity']
%                     Can be:
%                        'timidity' - Use the timidity synthesizer
%                        'flsynth'  - Use the Fluidsynth synthesizer
%   dsr      [num]  - Desired sampling rate [8000]
%   mono     [bool] - Render mono? [1]
%   tempo    [num]  - Tempo (in BPM) to render MIDI file at.  Empty means  
%                     leave the note matrix alone [[]]
%   vel      [num]  - Velocity to use for notes in nmat.  If [], leave
%                     alone [[]]
%   cfg      [str]  - Timidity config file to use ['']
%   trim_end [num]  - If greater than 0, the rendered output will be
%                     'trimmed' so that excess silence is removed. The
%                     actual value of trim_end is used as a threshold.  
%                     If 0, we trim based on the implied length of nmat, 
%                     if negative, we don't trim at all. [-1]
%   decay    [num]  - If trim_end is 0, decay number of seconds will be
%                     included to account for decay [0]
%   insts    [vec]  - General MIDI numbers to use for each channel.
%                     If empty, leave things alone. [[]]
%
% Output:
%   dat      [mat]  - Audio samples (nsamples x nchannels)
%   sr       [num]  - Sampling rate of audio samples.  Just returned for
%                     convenience.
%

% Copyright (C) 2010-2030 Graham Grindlay (grindlay@ee.columbia.edu)
%
% This program is free software: you can redistribute it and/or modify
% it under the terms of the GNU General Public License as published by
% the Free Software Foundation, either version 3 of the License, or
% (at your option) any later version.
%
% This program is distributed in the hope that it will be useful,
% but WITHOUT ANY WARRANTY; without even the implied warranty of
% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
% GNU General Public License for more details.
%
% You should have received a copy of the GNU General Public License
% along with this program.  If not, see <http://www.gnu.org/licenses/>.

% process arguments
[synth, dsr, mono, tempo, vel, cfg, trim_end, decay, insts] = ...
    parse_opt(varargin, 'synth', 'timidity', ...
                        'dsr', 8000, ...
                        'mono', 1, ...
                        'tempo', [], ...
                        'vel', [], ...
                        'cfg', '', ...
                        'trim_end', -1, ...
                        'decay', 0, ...
                        'insts', []);

% ensure that the number of channels in the nmat and the number of 
% instruments are the same  
nchan = length(unique(channel(nmat)));
ninst = length(insts);
if nchan > ninst
    warning(['More channels in nmat than instrument numbers - ' ...
             'repeating last instrument number.']);
    if ninst == 0
        warning('No instrument numbers supplied, so using default of 0');
        insts = 0;
        ninst = 1;
    end
    insts = [insts insts(end)*ones(1, nchan-ninst)];
elseif nchan < ninst
    warning(['Fewer channels in nmat than instrument numbers - ' ...
             'removing last instrument numbers.']);
    insts = insts(1:nchan);
end

switch synth
    case 'timidity'
        % first make sure that the timidity program is installed
        [status,res] = system('type timidity');
        if status
            error('timidity doesn''t seem to be installed; please check your system configuration');
        end
        
        cmd = 'timidity ';
        
        if ~isempty(cfg)
            cmd = [cmd ' -c ' cfg];
        end
        
        cmd = [cmd ' '];
        
        if mono
            cmd = [cmd '--output-mono '];
        end
        
        tmpmidi = [tempname() '.mid'];
        
        if ~isempty(vel)
            nmat(:,5) = vel;
        end
        
        if ~isempty(tempo)
            nmat = settempo(nmat, tempo);
        else
            tempo = gettempo(nmat);
        end
        
        % keep track of start time of first note
        stime = nmat(1,6);
        
        % write out the nmat with instruments and (possibly) updated tempo
        writemidi(nmat, tmpmidi, 120, tempo, 4, 4, insts);
        
        tmpwav = [tempname() '.wav'];
        cmd = [cmd '-s ' num2str(dsr) ' ' tmpmidi ' -Ow -o ' tmpwav];
        
    case 'flsynth'
        % first make sure that the fluidsynth program is installed
        [status,res] = system('type fluidsynth');
        if status
            error('fluidsynth doesn''t seem to be installed; please check your system configuration');
        end
        
        cmd = 'fluidsynth -nl ';
        
        if ~isempty(cfg)
            cmd = [cmd '-f ' cfg];
        end
        
        cmd = [cmd ' '];
        
        tmpmidi = [tempname() '.mid'];
        
        if ~isempty(vel)
            nmat(:,5) = vel;
        end
        
        if ~isempty(tempo)
            nmat = settempo(nmat, tempo);
        else
            tempo = gettempo(nmat);
        end
        
        % keep track of start time of first note
        stime = nmat(1,6);
        
        % write out the nmat with instruments and (possibly) updated tempo
        writemidi(nmat, tmpmidi, 120, tempo, 4, 4, insts);
        
        tmpwav = [tempname() '.wav'];
        cmd = [cmd '-r ' num2str(dsr) ' -F ' tmpwav ' ' tmpmidi];
        
    otherwise
        error([synth ' is an unknown software synthesis option' ]);
end

% now call timidity to synthesize the file to a temporary file
[status,res] = system(cmd);
[dat,sr] = wavread(tmpwav);

% TODO: figure out how to do this directly with FluidSynth
if size(dat,2) == 2 && mono
    dat = mean(dat,2);
end

% this is a hack that compensates for timidity's inability to render
% leading silence for files that do not begin at time 0
if strcmp(synth, 'timidity')
    dat = [zeros(round(stime*sr),1) ; dat];
end

% finally, do trimming if asked
if trim_end > 0
    dat = trim_by_env(dat, trim_end);
elseif trim_end == 0
    % factor in decay allowance
    if decay > 0
        len = min(round((nmat_dur(nmat)+decay)*sr), length(dat));
    else
        len = round(nmat_dur(nmat)*sr);
    end
    dat = dat(1:len);
end

% TODO: update code when changeset 389 is integrated into main FluidSynth
% for some reason the SR is limited to a min of 22.5kHz in FS
if dsr ~= sr
    dat = resample(dat, dsr, sr);
end
